package me.goldze.mvvmhabit.utils;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import com.trello.rxlifecycle2.LifecycleProvider;
import com.trello.rxlifecycle2.LifecycleTransformer;

import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;

import io.reactivex.FlowableTransformer;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import me.goldze.mvvmhabit.base.BaseViewModel;
import me.goldze.mvvmhabit.http.BaseBean;
import me.goldze.mvvmhabit.http.ExceptionHandle;
import me.goldze.mvvmhabit.http.NoDataException;
import me.goldze.mvvmhabit.http.UpdateResult;
import me.goldze.mvvmhabit.utils.cache.ACache;
import retrofit2.HttpException;

/**
 * Created by goldze on 2017/6/19.
 * 有关Rx的工具类
 */
public class RxUtils {
    public static <T> Observable<BaseBean<T>> bindLocal(T t, String errorInfo) {
        BaseBean bean = new BaseBean();
        NullUtils.isNullAble(t).isNull(() -> {
            bean.setData(t);
            bean.setCode(200);
            bean.setinfo("查询成功");
        }, () -> {
            bean.setData(t);
            bean.setCode(-1000);
            bean.setinfo(errorInfo);
        });
        return Observable.just(bean);
    }

    /**
     * 线程调度，生命周期绑定
     *
     * @param lifecycle 生命周期
     * @return {@link ObservableTransformer}<{@link T}, {@link T}>
     */
    public static <T> ObservableTransformer<T, T> applyIOThread(@NonNull LifecycleProvider lifecycle, BaseViewModel vm) {
        return observable -> observable
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(tBaseBean -> {
                    vm.getUC().getShowLoadingEvent().postValue("title");
                })
                .doFinally(() -> vm.getUC().getDismissLoadingEvent().call())
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .compose(bindToLifecycle(lifecycle))
                .onErrorResumeNext((Function<Throwable, Observable<T>>) t -> Observable.error(ExceptionHandle.handleException(t)))
                .observeOn(AndroidSchedulers.mainThread())
                .doOnError(throwable -> {
                    showToast(throwable);
                });
    }


    public static <T> ObservableTransformer<BaseBean<T>, T> applySchedulersIO(@NonNull LifecycleProvider lifecycle, BaseViewModel vm) {
        return applySchedulers(lifecycle, vm, false, true, false);
    }


    public static <T> ObservableTransformer<BaseBean<T>, T> applySchedulersLoading(@NonNull LifecycleProvider lifecycle, BaseViewModel vm) {
        return applySchedulers(lifecycle, vm, true, true, false);
    }

    public static <T> ObservableTransformer<BaseBean<T>, T> applySchedulersDialog(@NonNull LifecycleProvider lifecycle, BaseViewModel vm) {
        return applySchedulers(lifecycle, vm, true, false, true);
    }

    public static <T> ObservableTransformer<BaseBean<T>, T> applySchedulersToast(@NonNull LifecycleProvider lifecycle, BaseViewModel vm) {
        return applySchedulers(lifecycle, vm, true, true, false);
    }

    public static <T> FlowableTransformer<BaseBean<T>, T> applySchedulersIOFlowable(@NonNull LifecycleProvider lifecycle, BaseViewModel vm) {
        return applySchedulersFlowable(lifecycle, vm, false, true, false);
    }

    public static <T> ObservableTransformer<BaseBean<T>, T> applyRobotSchedulers(@NonNull LifecycleProvider lifecycle, BaseViewModel vm) {
        return observable ->
                observable.observeOn(AndroidSchedulers.mainThread())
                        .doOnSubscribe(tBaseBean -> {
                            KLog.e("----------");
                            vm.getUC().getShowRobotLoadingEvent().call();
                        })
                        .doFinally(() -> {
                            KLog.e("-----++-----");
                            vm.getUC().getHideRobotLoadingEvent().call();
                        })
                        .subscribeOn(Schedulers.io())
                        .observeOn(Schedulers.io())
                        .compose(bindToLifecycle(lifecycle))
                        .map(new HandleFuc<>())
                        .observeOn(AndroidSchedulers.mainThread());
    }

    public static <T> FlowableTransformer<BaseBean<T>, T> applySchedulersFlowable(@NonNull LifecycleProvider lifecycle, BaseViewModel vm, boolean showLoading, boolean showToast, boolean showDialog) {
        return observable ->
                observable.observeOn(AndroidSchedulers.mainThread())
                        .doOnSubscribe(tBaseBean -> {
                            if (showLoading)
                                vm.getUC().getShowLoadingEvent().postValue("title");
                        })
                        .doFinally(() -> {
                            if (showLoading)
                                vm.getUC().getDismissLoadingEvent().call();
                        })
                        .subscribeOn(Schedulers.io())
                        .observeOn(Schedulers.io())
                        .compose(bindToLifecycle(lifecycle))
                        .map(new HandleFuc<T>())
                        .observeOn(AndroidSchedulers.mainThread())
                        .doOnError(throwable -> {
                            if (showToast)
                                showToast(throwable);
                            if (showDialog)
                                showErrorDialog(vm, throwable);
                        });
    }

    public static <T> ObservableTransformer<BaseBean<T>, T> applySchedulers(@NonNull LifecycleProvider lifecycle, BaseViewModel vm, boolean showLoading, boolean showToast, boolean showDialog) {
        return observable -> observable
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(tBaseBean -> {
                    if (showLoading)
                        vm.getUC().getShowLoadingEvent().postValue("title");
                })
                .doFinally(() -> {
                    if (showLoading)
                        vm.getUC().getDismissLoadingEvent().call();
                })
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .compose(bindToLifecycle(lifecycle))
                .map(new HandleFuc<T>())
                .onErrorResumeNext((Function<Throwable, Observable<T>>) t -> Observable.error(ExceptionHandle.handleException(t)))
                .observeOn(AndroidSchedulers.mainThread())
                .doOnError(throwable -> {
                    if (showToast)
                        showToast(throwable);
                    if (showDialog)
                        showErrorDialog(vm, throwable);
                });
    }

    public static class RetryNoBook implements Function<Observable<Throwable>, ObservableSource<?>> {

        private int maxRetry;
        private int delay;
        private int retryCount = 0;

        private Observable<BaseBean<UpdateResult>> registerObservable;

        public RetryNoBook(Observable<BaseBean<UpdateResult>> observable, int maxRetry, int delay) {
            this.registerObservable = observable;
            this.maxRetry = maxRetry;
            this.delay = delay;
            retryCount = 0;
        }

        @Override
        public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
            return throwableObservable.flatMap(throwable -> {
                if (throwable instanceof NoDataException) {
                    if (++retryCount <= maxRetry) {
                        return registerObservable.flatMap(observable -> {
                            if (observable.isOk() && (observable.getData().getAffectNum() > 0 | observable.getData().getAffactNum() > 0)) {
                                return Observable.timer(delay, TimeUnit.MILLISECONDS);
                            } else {
                                if (observable.getCode() == 400) {
                                    throw new NoDataException(400, "标签自动注册失败：400");
                                }
                                return Observable.error(throwable);
                            }
                        }).subscribeOn(Schedulers.io());
                    } else {
                        return Observable.error(throwable);
                    }
                } else {
                    return Observable.error(throwable);
                }
            }).subscribeOn(Schedulers.io());
        }
    }

    private static final int MSG_LOADING = 1;
    private static final int MSG_TOAST = 2;
    private static final int MSG_DIALOG = 3;

    private static Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@androidx.annotation.NonNull Message msg) {
            MsgBean bean = (MsgBean) msg.obj;
            KLog.e(bean.toString() + "  " + msg.what);
            switch (msg.what) {
                case MSG_TOAST:
                    showToast(bean.throwable);
                    break;
                case MSG_LOADING:
                    if (bean.show) {
                        bean.vm.getUC().getShowLoadingEvent().postValue("title");
                    } else {

                    }
                    break;
                case MSG_DIALOG:
                    showErrorDialog(bean.vm, bean.throwable);
                    break;
            }
        }
    };

    private static class MsgBean {
        public BaseViewModel vm;
        public boolean show;
        public Throwable throwable;

        @Override
        public String toString() {
            return "MsgBean{" +
                    "vm=" + vm +
                    ", show=" + show +
                    ", throwable=" + throwable +
                    '}';
        }
    }


    private static void showToast(Throwable throwable) {
        if (throwable instanceof ExceptionHandle.ResponeThrowable) {
            ToastUtils.showShort(((ExceptionHandle.ResponeThrowable) throwable).message);
        } else {
            ToastUtils.showShort(throwable.getMessage());
        }
    }

    private static void showErrorDialog(BaseViewModel vm, Throwable throwable) {
        if (throwable instanceof ExceptionHandle.ResponeThrowable) {
            vm.getUC().getOnError().postValue(((ExceptionHandle.ResponeThrowable) throwable).message);
        } else {
            vm.getUC().getOnError().postValue(throwable.getMessage());
        }
    }

    public static class RetryWithTimeOut implements Function<Observable<Throwable>, ObservableSource<?>> {

        private int maxRetry;
        private int delay;
        private int retryCount = 0;

        public RetryWithTimeOut(int maxRetry, int delay) {
            this.maxRetry = maxRetry;
            this.delay = delay;
            retryCount = 0;
        }


        @Override
        public ObservableSource<?> apply(Observable<Throwable> throwableObservable) throws Exception {
            return throwableObservable.flatMap(throwable -> {
                if (throwable instanceof SocketException | throwable instanceof SocketTimeoutException) {
                    if (++retryCount <= maxRetry) {
                        return Observable.timer(delay, TimeUnit.MILLISECONDS);
                    } else {
                        return Observable.error(throwable);
                    }
                } else {
                    return Observable.error(throwable);
                }
            }).subscribeOn(Schedulers.io());
        }
    }


    /**
     * 生命周期绑定
     *
     * @param lifecycle Fragment
     */
    public static <T> LifecycleTransformer<T> bindToLifecycle(@NonNull LifecycleProvider lifecycle) {
        return lifecycle.bindToLifecycle();
    }


    /**
     * 数据转换
     */
    public static class HandleFuc<T> implements Function<BaseBean<T>, T> {
        @Override
        public T apply(BaseBean<T> response) {
            if (!response.isOk())
                throw new IllegalArgumentException(!"".equals(response.getCode() + "" + response.getinfo()) ? response.getinfo() : "");
            if (response.getData() == null) {
                throw new IllegalArgumentException("查无此数据");
            }
            if (response.getData() instanceof UpdateResult) {
                if (((UpdateResult) response.getData()).getAffactNum() == 0 && ((UpdateResult) response.getData()).getAffectNum() == 0) {
                    throw new IllegalArgumentException("操作失败，请检查该层架或者图书");
                }
            }
            return response.getData();
        }
    }
}
